<?php

/**
 * Class SyncTcpServer
 */
Class SyncTcpServer
{

    protected $protocol;
    protected $host;
    protected $port;
    protected $errorNo;
    protected $errorMsg;

    protected $requestConnections;
    protected $clientConnections;

    protected $registerActions = array();

    /**
     * AwaitTcpServer constructor.
     *
     * @param string    $host
     * @param integer   $port
     */
    public function __construct($host, $port)
    {
        $this->host = $host;
        $this->port = $port;
        $this->protocol = 'tcp';
        $this->connections = array();
        $this->requestConnections = array();
        $this->clientConnections = array();

        $this->registerActions = array(
            'accept' => function () {},
            'receive' => function () {},
            'close' => function () {},
        );
    }

    /**
     * @param String  $action
     * @param Closure $closure
     */
    public function on($action, Closure $closure)
    {
        $this->registerActions[$action] = $closure;
    }

    /**
     * @param string    $str
     * @param integer   $len
     */
    public function console($str, $len = null)
    {
        fwrite(STDOUT, $str);
    }

    private function onClose($socket, $info)
    {
        $this->registerActions['close'] ($this, $socket, $info);

    }

    final public function run()
    {
        $serverSocket = @stream_socket_server($this->protocol . '://' . $this->host . ':' . $this->port, $this->errorNo, $this->errorMsg);
        if ($serverSocket === false) {
            throw new \RuntimeException("fail to listen on port: {$this->port}!");
        }
        $this->console("socket server is listening : {$this->host}:{$this->port}" . PHP_EOL);
        stream_set_blocking($serverSocket, false);

        $keepRun = true;
        while ($keepRun) {

            $allConnections = array_merge($this->requestConnections, $this->clientConnections);
            // listen for new connection
            if ($c = @stream_socket_accept($serverSocket, empty($allConnections) ? -1 : 0, $peer)) {
                stream_set_blocking($c, false);
                $this->requestConnections[$peer] = $c;
                $allConnections[] = $c;

                // user callback
                $this->registerActions['accept'] ($this, $c, array('pearName' => $peer));
            }

            if (stream_select($allConnections, $write, $except, 5)) {
                foreach ($this->requestConnections as $key => $requestConnection) {
                    $peer = stream_socket_get_name($requestConnection, true);
                    $contents = $part = '';

                    do {
                        $part = fread($requestConnection, 4096);
                        $contents .= $part;
                    } while ($part);
                    // user callback
                    if (empty($contents)) {
                        // 关闭请求
                        $this->onClose($requestConnection, array('pearName' => $peer));
                        unset($this->requestConnections[$key]);
                    } else {
                        // 数据请求
                        $this->registerActions['receive'] ($this, $requestConnection, array('pearName' => $peer), $contents);
                    }
                }

                // client response
                foreach ($this->clientConnections as $clientConnection) {
                    $peer = stream_socket_get_name($clientConnection, true);

                    do {
                        $part = fread($clientConnection, 4096);
                        $contents .= $part;
                    } while ($part);
                    // user callback
                    if (empty($contents)) {
                        // 关闭请求
                        $this->onClose($clientConnection, array('pearName' => $peer));
                        unset($this->clientConnections[$key]);
                    } else {
                        $this->registerActions['receive'] ($this, $clientConnection, array('pearName' => $peer), $contents);
                    }
                }
            }
        }  // end while()
    }

    /**
     * @param $socket
     * @return bool
     */
    public function close($socket)
    {
        foreach ($this->requestConnections as $requestConnection) {
            $peer = stream_socket_get_name($requestConnection, true);
            if (isset($this->requestConnections[$peer])) {
                unset($this->requestConnections[$peer]);
            }
        }

        // client response
        foreach ($this->clientConnections as $clientConnection) {
            $peer = stream_socket_get_name($clientConnection, true);
            if (isset($this->clientConnections[$peer])) {
                unset($this->clientConnections[$peer]);
            }
        }
        return fclose($socket);
    }

}

/**
 * Class SyncTcpClient
 */
class SyncTcpClient
{
    protected $protocol;
    protected $host;
    protected $port;

    /**
     * @var resource    $clientConnection
     */
    protected $clientConnection;

    /**
     * SyncTcpClient constructor.
     */
    public function __construct()
    {
        $this->protocol = 'tcp';
    }

    /**
     * @param      $host
     * @param      $port
     * @param null $timeout
     * @return false|resource
     * @throws \RuntimeException
     */
    public function connection($host, $port, $timeout = null)
    {
        $this->host = $host;
        $this->port = $port;

        if (null === $timeout) {
            $timeout = ini_get("default_socket_timeout");
        }
        $this->timeout = $timeout;

        $this->clientConnection = stream_socket_client("{$this->protocol}://{$this->host}:{$this->port}", $errorNo, $errorMsg, $timeout, STREAM_CLIENT_CONNECT);
        if ($this->clientConnection === false) {
            throw new \RuntimeException("unable to create socket: " . $errorMsg . ', code: ' . $errorNo);
        }
        return $this->clientConnection;
    }

    public function send($data)
    {
        return fwrite($this->clientConnection, $data);
    }

    public function receive($length = 1024)
    {
        $contents = '';
        while (!feof($this->clientConnection)) {
            $contents = fread($this->clientConnection, $length);
        }
        return $contents;
    }

    public function close()
    {
        if ($this->clientConnection) {
            fclose($this->clientConnection);
        }
    }

}